package ru.nosport.matrixaria.core;

import ru.nosport.matrixaria.modules.common.AppServerProcess;

import java.util.*;

/**
 * 
 * User: vfabr
 * Date: 19.09.2006
 * Time: 15:59:45
 *
 */

class Registry {
	private static Registry ourInstance = new Registry();
	private Map<String, RegistryContainer> moduleHash;
	private Map<String, ModuleShortcut> onflyShortcutHash;

	public static Registry getInstance() {
		return ourInstance;
	}

	/**
	 *
	 */
	private Registry() {
		this.moduleHash = new HashMap<String, RegistryContainer>(16, 1.7f);
		this.onflyShortcutHash = new HashMap<String, ModuleShortcut>(16, 1.7f);
	}

	/**
	 * Load module by it's name.
	 *
	 * @param _name - module name.
	 * @param _module - reference on module.
	 * @throws RegistryException - if module exists.
	 */
	public synchronized void loadModule(String _name, Object _module) throws RegistryException {
		this.checkNullModuleName(_name);
		this.checkNullModule(_module);

		if (!this.existsModule(_name)) {
			RegistryContainer regCont = new RegContainer(_module);
			this.startAppServerProcess(_name, regCont);
			this.moduleHash.put(_name, regCont);
		} else {
			String errMessage = "Module " + _name + " already exists and belongs to the class " + this.getRegCont(_name).getObjClassName();
			throw new RegistryException(errMessage);
		}
	}

	/**
	 * Load onFly module shortcut.
	 *
	 * @param _name - module name.
	 * @param _shortcut - module shortcut.
	 * @throws RegistryException - if module exists.
	 */
	public synchronized void loadOnFlyModule(String _name, ModuleShortcut _shortcut) throws RegistryException {
		this.checkNullModuleName(_name);
		if(_shortcut == null){
			throw new IllegalArgumentException("_shortcut is null. Module name ["+_name+"]");
		}

		if (!this.onflyShortcutHash.containsKey(_name)) {
			this.onflyShortcutHash.put(_name, _shortcut);
		} else {
			String errMessage = "Module " + _name + " on fly already exists.";
			throw new RegistryException(errMessage);
		}
	}

	/**
	 * Start AppServerProcess. Starting applications is happening in one stream.
	 * The reason for this simplification of services are dependent on other services.
	 *
	 * @param _name - AppServerProcess name.
	 * @param _regCont - registry container.
	 * @throws RegistryException - wrap any Exception.
	 */
	private void startAppServerProcess(String _name, RegistryContainer _regCont) throws RegistryException {
		try {
			((AppServerProcess) _regCont.getModule()).moduleStart();
			_regCont.releaseModule();
		} catch (Exception _ex) {
			throw new RegistryException("Exception thrown during startup in module " + _name + ": " + _ex.getMessage());
		}
	}

	/**
	 * @param _module -
	 */
	private void checkNullModule(Object _module) {
		if (_module == null) {
			throw new IllegalArgumentException("_module can not be null.");
		}
	}

	/**
	 * Unload module from registry.
	 *
	 * @param _name - module name to unload.
	 * @throws RegistryException - when module being unloaded is busy.
	 */
	public void unloadModule(String _name) throws RegistryException {
		this.checkNullModuleName(_name);

		if (this.existsModule(_name)) {
			boolean canDestroy;
			RegistryContainer regCont = getRegCont(_name);
			synchronized(this) {
				if (regCont.getLinkCounter() == 0) {
					this.moduleHash.remove(_name);
					canDestroy = true;
				} else {
					String errMessage = "Module " + _name + " is busy. Can't unload.";
					throw new RegistryException(errMessage);
				}
			}
			
			if(canDestroy) {
				//TODO: Check module ability to be stopped.
				regCont.destroy();
				//TODO: regCont.destroy() calls moduleStop() that can "hang", or return false;
			}

		} else {
			String errMessage = "Module " + _name + " doesn't exist. Can't unload.";
			throw new NullPointerException(errMessage);
		}
	}

	/**
	 * Unload onfly module from registry.
	 *
	 * @param _module -
	 */
	public void unloadOnFlyModule(String _module){
		this.onflyShortcutHash.remove(_module);
	}

	/**
	 * @param _name - 
	 */
	private void checkNullModuleName(String _name) {
		if (_name == null) {
			throw  new IllegalArgumentException("_name can not be null.");
		}
	}

	/**
	 * Check for the module in the registry.
	 *
	 * @param _name - module name.
	 * @return - true if exists else false.
	 */
	public synchronized boolean existsModule(String _name) {
		return this.moduleHash.containsKey(_name);
	}

	/**
	 * Get reference on module.
	 *
	 * @param _name - module name.
	 * @return - reference on module.
	 * @throws RegistryException - if module not found.
	 */
	public synchronized Object getModule(String _name) throws RegistryException {
		this.checkNullModuleName(_name);

		if (moduleHash.containsKey(_name)) {
			return this.getRegCont(_name).getModule();
		} else {
			String errMessage = "Module name " + _name + " doesn't exist.";
			throw new RegistryException(errMessage);
		}

	}

	/**
	 * Get module shortcut.
	 *
	 * @param _name - module name.
	 * @return - module shortcut.
	 * @throws RegistryException - if the request is not an existing module.
	 */
	public synchronized ModuleShortcut getOnFlyShortcut(String _name) throws RegistryException {
		this.checkNullModuleName(_name);

		if (this.onflyShortcutHash.containsKey(_name)) {
			return this.onflyShortcutHash.get(_name);
		} else {
			String errMessage = "Shortcut on module " + _name + " doesn't exist.";
			throw new RegistryException(errMessage);
		}
	}

	/**
	 * The operation release the module after it was occupied by the method getModule()
	 *
	 * @param _name - module name.
	 * @param _module - reference on modele.
	 * @throws RegistryException - tried to remove non-existing module.
	 */
	public synchronized void releaseModule(String _name, Object _module) throws RegistryException {

		this.checkNullModuleName(_name);
		this.checkNullModule(_module);

		if (checkModuleName(_name, _module)) {
			getRegCont(_name).releaseModule();
		} else {
			String errMessage = "Module name " + _name + " does not correspond to object {" + _module.hashCode() + "} "
					+ this.getRegCont(_name).getObjClassName();
			throw new RegistryException(errMessage);
		}
	}

	/**
	 * Checks whether instance name corresponds to given object
	 *
	 * @param _name - module name.
	 * @param _module - instance shortcut.
	 * @return - true if object exists in registry with the name given.
	 * @throws RegistryException -
	 */
	private boolean checkModuleName(String _name, Object _module) throws RegistryException {
		if (getRegCont(_name) == null) {
			String errMessage = "Module name " + _name + " doesn't exist.";
			throw new RegistryException(errMessage);
		}

		boolean ret = (getRegCont(_name).getModule() == _module);
		getRegCont(_name).releaseModule();
		return ret;
	}

	/**
	 * Returns container by it's name.
	 *
	 * @param _name - container's name
	 * @return - container for the specified module
	 */
	private RegistryContainer getRegCont(String _name) {
		return moduleHash.get(_name);
	}


	/**
	 * Filters module names list by class name.
	 *
	 * @param _className - full class name (example: java.lang.String).
	 * @return set of module names.
	 */
	public synchronized Set<String> getModuleList(String _className) {
		Set<String> moduleList = new HashSet<String>(16);
		for (String key : this.getAllModuleList()) {
			String className = this.getRegCont(key).getObjClassName();
			if (className.equals(_className)) {
				moduleList.add(key);
			}
		}
		return moduleList;
	}

	/**
	 *
	 * @param _interfaceName -
	 * @return -
	 */
	public synchronized Set<String> getImplementsModuleList(String _interfaceName) {
		Set<String> moduleList = new HashSet<String>(16);
		try {
			Class _class = Class.forName(_interfaceName);
			for (String key : this.getAllModuleList()) {
				Class cls = Class.forName(this.getRegCont(key).getObjClassName());
				List<Class> interfaces = new ArrayList<Class>(Arrays.asList(cls.getInterfaces()));
				while (cls != null) {
					interfaces.add(cls);
					cls = cls.getSuperclass();
				}

				if (interfaces.contains(_class)) {
					moduleList.add(key);
				}
			}
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		return moduleList;
	}

	/**
	 * Get set of all module's names.
	 *
	 * @return - set of all module's names.
	 */
	public synchronized Set<String> getModuleList() {
		return this.getAllModuleList();
	}

	/**
	 * @return - set of all module's names.
	 */
	private Set<String> getAllModuleList() {
		return this.moduleHash.keySet();
	}

}
